/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/io.c,v 1.6 2002/07/08 06:55:03 bboy Exp $
	io.c: Routines to read and write queries/responses across sockets.

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"

extern int exit_after_one;


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	READ_UDP_QUERY
	Returns 0 on success (a task was added), -1 on failure.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
read_udp_query(fd)
	int fd;
{
	struct sockaddr_in addr;
	u_char in[DNS_MAXPACKETLEN_UDP], *src;
	int addrlen, inbytes, qclass;
	u_char qname[DNS_MAXNAMELEN];
	TASK *t;
	u_int16_t ancount, nscount, arcount;

	/* Read message */
	addrlen = sizeof(addr);
	memset(&in, 0, sizeof(in));
	if ((inbytes = recvfrom(fd, &in, sizeof(in), 0, (struct sockaddr *)&addr, &addrlen)) == -1)
		return Warn("recvfrom");

	/* Start new task */
	t = task_init(NEED_ANSWER, fd, SOCKTYPE_UDP, &addr);

#if DEBUG
	Debug("[%llu] %s: %d %s", t->internal_id, inet_ntoa(t->addr.sin_addr), inbytes, _("bytes read"));
#endif

	if (inbytes < sizeof(DNS_HEADER))
	{
		task_free(t);
		return Warnx("%s: %s", inet_ntoa(addr.sin_addr), _("message too short"));
	}
	if (inbytes > DNS_MAXPACKETLEN_UDP)
	{
		task_free(t);
		return Warnx("%s: %s", inet_ntoa(addr.sin_addr), _("message too long"));
	}

	/* Parse query header data */
	src = in;
	DNS_GET16(t->id, src);
	memcpy(&t->hdr, src, SIZE16); src += SIZE16;
	DNS_GET16(t->qdcount, src);
	DNS_GET16(ancount, src);
	DNS_GET16(nscount, src);
	DNS_GET16(arcount, src);

#if DEBUG
	Debug("%s: id=%u qr=%u opcode=%s aa=%u tc=%u rd=%u ra=%u z=%u rcode=%u", desctask(t),
			t->id, t->hdr.qr, mydns_opcode_str(t->hdr.opcode),
			t->hdr.aa, t->hdr.tc, t->hdr.rd, t->hdr.ra, t->hdr.z, t->hdr.rcode);
	Debug("%s: qd=%u an=%u ns=%u ar=%u", desctask(t),
			t->qdcount, ancount, nscount, arcount);
#endif

	/* Check validity of header fields */
	if (!ntohs(t->qdcount))
	{
		task_free(t);
		return Warnx("%s: %s", inet_ntoa(addr.sin_addr), _("message does not contain any questions"));
	}
	if (t->hdr.tc)
	{
		task_free(t);
		return Warnx("%s: %s", inet_ntoa(addr.sin_addr), _("truncated request"));
	}
	if (ntohs(t->hdr.opcode) != DNS_OPCODE_QUERY)
	{
		task_free(t);
		return Warnx("%s: %s", inet_ntoa(addr.sin_addr), _("not a standard query"));
	}

	/* Fill in question data */
	t->qdlen = inbytes - DNS_HEADERSIZE;
	t->qd = (u_char *)MALLOC(t->qdlen);
	memcpy(t->qd, src, t->qdlen);

	/* Get QNAME, QTYPE, and QCLASS */
	if (!(src = name_unencode(t->qd, t->qdlen, src, qname, sizeof(qname))))
	{
		task_free(t);
		return Warnx("%s: %s", _("error getting name from question"), qname);
	}
	strncpy(t->qname, qname, sizeof(t->qname)-1);
	DNS_GET16(t->qtype, src);
	DNS_GET16(qclass, src);

	/* Enqueue new item */
	enqueue(Tasks, t);

	/* Check class (only IN or ANY are allowed) */
	if ((qclass != DNS_C_IN) && (qclass != DNS_C_ANY))
		return dnserror(t, DNS_R_NOTIMP);

#if DEBUG
	Debug("%s: qtype=%s qclass=%s qname=[%s]", desctask(t),
			mydns_qtype_str(t->qtype), mydns_class_str(qclass), t->qname);
#endif

	Verbose("%s: %u bytes, id %u", desctask(t), inbytes, t->id);

	return (0);
}
/*--- read_udp_query() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	WRITE_UDP_REPLY
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
write_udp_reply(t)
	TASK *t;
{
	int rv;

#if DEBUG
	Debug("%s: %s", desctask(t), _("sending reply"));
#endif

	if ((rv = sendto(t->fd, t->reply, t->replylen, MSG_DONTWAIT,
					(struct sockaddr *)&t->addr, sizeof(t->addr))) < 0)
	{
		if (errno == EAGAIN || errno == EINTR)
			return (0);
		Warn("%s: %s", desctask(t), _("error writing reply"));
		dequeue(Tasks, t, 1);
		return (-1);
	}

	/* Task complete; dequeue */
	Verbose("%s: reply %u bytes", desctask(t), t->replylen);
	dequeue(Tasks, t, 0);
	if (exit_after_one)
	{
		cache_empty();
		exit(EXIT_SUCCESS);
	}

	return (0);
}
/*--- write_udp_reply() -------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
